#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>
#include <sys/time.h>
#include <pthread.h>

#define NUMDIGITOS 10000001
#define NUMITERACOES 1000000
#define NUMTHREADS 2
#define PRECISAO_NUMERO_ALEATORIO 1000
#define LOG_2_10 3.32192809488736234787

int pontosTotal;
mpf_t x, y, z, pontosDentro, pi;
gmp_randstate_t numeroAleatorio;
pthread_mutex_t mutexSoma;

void *calculaPontosDentro(void *tid) {
    int iPontos;
    
    pthread_mutex_lock(&mutexSoma);
    for(iPontos = 0; iPontos < (NUMITERACOES / NUMTHREADS); iPontos++) {
        // X = rand[0,1]
        mpf_urandomb(x, numeroAleatorio, PRECISAO_NUMERO_ALEATORIO);

        // X = X^2
        mpf_pow_ui(x, x, 2);

        // Y = rand[0,1]
        mpf_urandomb(y, numeroAleatorio, PRECISAO_NUMERO_ALEATORIO);

        // Y = Y^2
        mpf_pow_ui(y, y, 2);

        // Z = X + Y
        mpf_add(z, x, y);

        // Se Z < 1
        if (mpf_cmp_d(z, 1.0) < 0.0) {
            // P(dentro) = P(dentro) + 1
            mpf_add_ui(pontosDentro, pontosDentro, 1);
        }
        
    }
    
    pthread_mutex_unlock(&mutexSoma);
}

int main(void) {
    // <editor-fold defaultstate="collapsed" desc="Inicia Contagem de Tempo">
    // Inicia contagem de tempo
    struct timeval tvInitial, tvFinal;
    double tInitial, tFinal;
    gettimeofday(&tvInitial, NULL);
    tInitial = (double) (tvInitial.tv_sec) + (double) (tvInitial.tv_usec) / 1000000.00; // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Declaracao das Variaveis">
    pthread_t thread[NUMTHREADS];
    long tid;
    int i, rc;
//    void *status;
    
    mpf_set_default_prec(LOG_2_10 * NUMDIGITOS);
    
    mpf_inits(x, y, z, pi, pontosDentro, NULL);
    gmp_randinit_default(numeroAleatorio);
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Execucao das Threads">
    for(i = 0; i < NUMTHREADS; i++) {
        rc = pthread_create(&thread[i], NULL, calculaPontosDentro, (void *)tid); 
        if (rc) {
           printf("ERRO: Codigo de retorno da pthread_create(): %d\n", rc);
           exit(-1);
        }
    }
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="Sincronizacao das Threads">
    for(i = 0; i < NUMTHREADS; i++) {
        rc = pthread_join(thread[i], NULL);
//        mpf_add(pontosDentro, pontosDentro, (float*) status);
        if (rc) {
           printf("ERRO: Codigo de retorno da pthread_join() : %d\n", rc);
           exit(-1);
        }
    }
    // </editor-fold>
    
    // PI = 4 * P(dentro) / P(total)
    mpf_div_ui(pi, pontosDentro, NUMITERACOES);
    mpf_mul_ui(pi, pi, 4);

    // <editor-fold defaultstate="collapsed" desc="Impressao dos Resultados">
    gmp_printf("PI(%d) = %.6Ff\n", NUMITERACOES, pi);
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Desalocacao das Variaveis">
    mpf_clears(x, y, z, NULL);
    mpf_clear(pontosDentro);
    mpf_clear(pi);
    gmp_randclear(numeroAleatorio); // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Finaliza Contagem de Tempo">
    gettimeofday(&tvFinal, NULL);
    tFinal = (double) (tvFinal.tv_sec) + (double) (tvFinal.tv_usec) / 1000000.00;
    // Imprime o tempo de execucao
    printf("Tempo de execucao: %.2lfs\n", (tFinal - tInitial)); // </editor-fold>

    // Retorno
    return EXIT_SUCCESS;

}
